home *** CD-ROM | disk | FTP | other *** search
/ NOVA - For the NeXT Workstation / NOVA - For the NeXT Workstation.iso / Apps / AudioApps / GISO / Subprocess.m < prev    next >
Text File  |  1992-12-20  |  8KB  |  388 lines

  1. /*----------------------------------------------------------------------------
  2.     Subprocess.m
  3.     
  4.     From Subprocess example by Charles L. Oei
  5.                                     pty support by Joe Freeman
  6.                                     with encouragement from Kristofer Younger
  7.                                     Subprocess Example, Release 2.0
  8.                                     NeXT Computer, Inc.
  9.     
  10.     You may freely copy, distribute and reuse the code in this example.
  11.     NeXT disclaims any warranty of any kind, expressed or implied, as to
  12.     its fitness for any particular use.
  13.         
  14.     SYNOPSIS
  15.         Handles a UNIX process that runs asynchronously.
  16.   
  17.     REVISIONS
  18.     Subprocess.m,v
  19. # Revision 1.1  1992/07/04  03:17:22  nwc
  20. # Initial revision
  21. #
  22. ----------------------------------------------------------------------------*/
  23. #import <sys/wait.h>
  24. #import <sys/resource.h>
  25. #import <appkit/nextstd.h>
  26. #import <appkit/Application.h>
  27. #import <appkit/Panel.h>
  28. #import "Subprocess.h"
  29.  
  30. extern int  wait4(int, union wait *, int, struct rusage *);
  31. static void fdHandler(int theFd, id self);
  32. static void stderrFdHandler(int theFd, id self);
  33.  
  34. #define PIPE_ERROR            "Error starting UNIX pipes to subprocess."
  35. #define VFORK_ERROR            "Error starting UNIX vfork of subprocess."
  36.  
  37. @interface Subprocess(Private)
  38. - childDidExit;
  39. - fdHandler:(int)theFd;
  40. @end
  41.  
  42. @implementation Subprocess(Private)
  43.  
  44. /*
  45.  * cleanup after a child process exits
  46.  */
  47. - childDidExit
  48. {
  49.    union wait  w;
  50.    int         status = 0;
  51.    int         thePid;
  52.  
  53.    thePid = wait4(childPid, &w, WUNTRACED, NULL);
  54. #ifdef DEBUG
  55.    fprintf(stderr, "%s: wait4() on process id #%d returned %d, with "
  56.        "w_status = %d, w_retcode = %d, w_stopval = %d, "
  57.        "w_stopsig = %d, w_termsig = %d\n",
  58.        [self name], childPid, thePid, w.w_status, w.w_retcode,
  59.        w.w_stopval, w.w_stopsig, w.w_termsig);
  60. #endif
  61.    if (thePid > 0)
  62.    {
  63.       if (WIFEXITED(w))
  64.      status = (w.w_status >> 8);
  65.       else
  66.       {
  67.      if (WIFSTOPPED(w))
  68.         status = SUBPROCESS_STOPPED;
  69.      else
  70.      {
  71.         if (WIFSIGNALED(w))
  72.            status = SUBPROCESS_SIGNALED;
  73.      }
  74.       }
  75.       DPSRemoveFD(fromChild);
  76.       DPSRemoveFD(stderrFromChild);
  77.       fclose(fpFromChild);
  78.       close(fromChild);
  79.       close(stderrFromChild);
  80.       fclose(fpToChild);
  81.       running = NO;
  82.       [delegate perform:@selector(subprocess:done:)
  83.        with :self
  84.        with:(void *)status];
  85.    }
  86.    return (self);
  87. }
  88.  
  89. /*
  90.  * DPS handler for output from subprocess
  91.  */
  92. - fdHandler:(int)theFd
  93. {
  94.    char       *s, *linep;
  95.    int         bufferCount;
  96.  
  97.    bufferCount = read(theFd, outputBuffer + outputBufferLen,
  98.               BUFSIZ - outputBufferLen);
  99.    if (bufferCount <= 0)
  100.    {
  101.       [self childDidExit];
  102.       return (self);
  103.    }
  104.    outputBuffer[bufferCount + outputBufferLen] = '\0';
  105.  
  106.    /*
  107.     * Send lines in the buffer to the delegate 
  108.     */
  109.    s = linep = outputBuffer;
  110.    while (s != NULL)
  111.    {
  112.       if ((s = index(linep, '\n')) != NULL)
  113.       {
  114.      *s = (char)0;
  115.      [delegate perform:@selector(subprocess:output:)
  116.       with :self
  117.       with:(void *)linep];
  118.      linep = s + 1;
  119.       }
  120.    }
  121.  
  122.    /*
  123.     * Copy the last part of the line back into the input buffer for next time (incomplete line) 
  124.     */
  125.    outputBufferLen = strlen(linep);
  126.    strncpy(outputBuffer, linep, outputBufferLen);
  127.  
  128.    return (self);
  129. }
  130.  
  131. @end
  132.  
  133. @implementation Subprocess
  134.  
  135. /*
  136.  * Cover for the init:withDelegate: with a nil delegate
  137.  */
  138. - init:(const char *)subprocessString
  139. {
  140.    return ([self init:subprocessString
  141.         withDelegate:nil]);
  142. }
  143.  
  144. - init:(const char *)subprocessString withDelegate:theDelegate
  145. {
  146.    int         pipeTo[2];
  147.    int         pipeFrom[2];
  148.    int         pipeStderr[2];            /* for stderr to different fd */
  149.    int         numFds, fd;
  150.    int         processGroup;
  151.  
  152.    [super init];
  153.  
  154.    outputBufferLen = 0;
  155.    stderrBufferLen = 0;
  156.    [self setDelegate:theDelegate];
  157.  
  158.    if (pipe(pipeTo) < 0 || pipe(pipeFrom) < 0 || pipe(pipeStderr) < 0)
  159.    {
  160.       [delegate perform:@selector(subprocess:error:)
  161.        with :self
  162.        with:(void *)PIPE_ERROR];
  163.       return (self);
  164.    }
  165.  
  166.    switch (childPid = vfork())
  167.    {
  168.       case -1:                    /* error */
  169.      [delegate perform:@selector(subprocess:error:)
  170.       with :self
  171.       with:(void *)VFORK_ERROR];
  172.      return (self);
  173.  
  174.       case 0:                    /* child */
  175.      dup2(pipeTo[0], 0);
  176.      dup2(pipeFrom[1], 1);            /* get stdout from process */
  177.      dup2(pipeStderr[1], 2);        /* get stderr here */
  178.  
  179.      numFds = getdtablesize();
  180.      for (fd = 3; fd < numFds; fd++)
  181.         close(fd);
  182.  
  183.      processGroup = getpid();
  184.      ioctl(0, TIOCSPGRP, (char *)&processGroup);
  185.      setpgrp(0, processGroup);
  186.  
  187.      /*
  188.       * we exec a /bin/sh so that cmds are easier to specify for the user 
  189.       */
  190.      execl("/bin/sh", "sh", "-c", subprocessString, 0);
  191.      perror("vfork (child)");        /* should never gets here tho */
  192.      exit(1);
  193.  
  194.       default:                    /* parent */
  195.      running = YES;
  196.  
  197.      close(pipeTo[0]);
  198.      close(pipeFrom[1]);
  199.      close(pipeStderr[1]);
  200.  
  201.      fpToChild = fdopen(pipeTo[1], "w");
  202.      fromChild = pipeFrom[0];
  203.      fpFromChild = fdopen(pipeFrom[0], "r");
  204.  
  205.      stderrFromChild = pipeStderr[0];
  206.  
  207.      /*
  208.       * Set buffering method, also make it use its own buffers 
  209.       */
  210.      setbuf(fpToChild, NULL);        /* no buffering */
  211.      setbuf(fpFromChild, NULL);
  212.      DPSAddFD(fromChild, (DPSFDProc) fdHandler, (id) self,
  213.           NX_MODALRESPTHRESHOLD + 1);
  214.  
  215.      DPSAddFD(stderrFromChild, (DPSFDProc) stderrFdHandler, (id) self,
  216.           NX_MODALRESPTHRESHOLD + 1);
  217.  
  218.      return (self);
  219.    }
  220. }
  221.  
  222.  
  223. - stderrFdHandler:(int)theFd
  224. {
  225.    char       *s, *linep;
  226.    int         bufferCount;
  227.  
  228.    bufferCount = read(theFd, stderrBuffer + stderrBufferLen,
  229.               BUFSIZ - stderrBufferLen);
  230.    if (bufferCount <= 0)
  231.       return (self);
  232.  
  233.    stderrBuffer[bufferCount + stderrBufferLen] = '\0';
  234.  
  235.    /*
  236.     * Send lines in the buffer to the delegate 
  237.     */
  238.    s = linep = stderrBuffer;
  239.    while (s != NULL)
  240.    {
  241.       if ((s = index(linep, '\n')) != NULL)
  242.       {
  243.      *s = (char)0;
  244.      [delegate perform:@selector(subprocess:stderrOutput:)
  245.       with :self
  246.       with:(void *)linep];
  247.      linep = s + 1;
  248.       }
  249.    }
  250.  
  251.    /*
  252.     * Copy the last part of the line back into the input buffer for next time (incomplete line) 
  253.     */
  254.    stderrBufferLen = strlen(linep);
  255.    strncpy(stderrBuffer, linep, stderrBufferLen);
  256.  
  257.    return (self);
  258. }
  259.  
  260. - send:(const char *)string withNewline:(BOOL) wantNewline
  261. {
  262.    fputs(string, fpToChild);
  263.    if (wantNewline)
  264.       fputc('\n', fpToChild);
  265.    return (self);
  266. }
  267.  
  268. - send:(const char *)string
  269. {
  270.    [self send:string withNewline:YES];
  271.    return (self);
  272. }
  273.  
  274. /*
  275.  * Returns the process id of the process (and therefore the process group
  276.  * of the job)
  277.  */
  278. - (int)pid
  279. {
  280.    return (childPid);
  281. }
  282.  
  283. - (BOOL) isPaused
  284. {
  285.    return (paused);
  286. }
  287.  
  288. - resume:sender
  289. {
  290.    if (paused)
  291.    {
  292.       killpg(childPid, SIGCONT);        /* resume the process group */
  293.       paused = NO;
  294.    }
  295.    return (self);
  296. }
  297.  
  298. - pause:sender
  299. {
  300.    if (!paused)
  301.    {
  302.       killpg(childPid, SIGSTOP);        /* pause the process group */
  303.       paused = YES;
  304.    }
  305.    return (self);
  306. }
  307.  
  308. - (BOOL) isRunning
  309. {
  310.    return (running);
  311. }
  312.  
  313. - terminate:sender
  314. {
  315.    if (running)
  316.       killpg(childPid, SIGKILL);
  317.    return (self);
  318. }
  319.  
  320. /*
  321.  * effectively sends an EOF to the child process stdin
  322.  */
  323. - terminateInput
  324. {
  325.    fclose(fpToChild);
  326.    return (self);
  327. }
  328.  
  329. - setDelegate:anObject
  330. {
  331.    delegate = anObject;
  332.    return (self);
  333. }
  334.  
  335. - delegate
  336. {
  337.    return (delegate);
  338. }
  339.  
  340. @end
  341.  
  342. @implementation Object(SubprocessDelegate)
  343.  
  344. - subprocess: sender done:(int)exitStatus
  345. {
  346.    return (self);
  347. }
  348.  
  349. - subprocess:sender output:(char *)buffer
  350. {
  351.    return (self);
  352. }
  353.  
  354. - subprocess:sender stderrOutput:(char *)buffer
  355. {
  356.    return (self);
  357. }
  358.  
  359. - subprocess:sender error:(const char *)errorString
  360. {
  361.    if (NXApp)
  362.       NXRunAlertPanel(0, errorString, 0, 0, 0);
  363.    else
  364.       perror(errorString);
  365.    return (self);
  366. }
  367.  
  368. @end
  369.  
  370.  
  371. /*
  372.  * And standard error from subprocess
  373.  */
  374. static void stderrFdHandler(int theFd, id self)
  375. {
  376.                [self stderrFdHandler:theFd];
  377. }
  378.  
  379. /*
  380.  * DPS handler for output from subprocess
  381.  */
  382. static void fdHandler(int theFd, id self)
  383. {
  384.                [self fdHandler:theFd];
  385. }
  386.  
  387.  
  388.